// Lark Grammar for Colang 2.x
?start: _statements | suite

// General sequences of statements
_statements: stmt*
?stmt: (def_stmt | flow_stmt | comment _NEWLINE | _NEWLINE)
suite: _NEWLINE _INDENT (stmt+) _DEDENT

// Flow definition and calling
?def_stmt: flow_def
decorators: (decorator _NEWLINE)*
decorator: "@" decorator_name ["(" decorator_parameters ")"]
decorator_parameters: _argvalues*
flow_def: decorators _FLOW spec_name [flow_params_def] ["->" return_members_def] suite
?flow_params_def: flow_params_def_classic | flow_params_def_simple
flow_params_def_classic: "(" (flow_param_def ("," flow_param_def)*) ")"
flow_params_def_simple: (flow_param_def)+
flow_param_def: var_name ("=" test)?
?flow_stmt: simple_stmt | spec_op_stmt | compound_stmt | doc_string_stmt
return_members_def: return_member_def ("," return_member_def)*
return_member_def: var_name ("=" test)?


// Simple statements
?simple_stmt: set_stmt | return_stmt | abort_stmt | break_stmt | continue_stmt | pass_stmt | log_stmt | print_stmt | priority_stmt | label_stmt | global_stmt | expr_stmt | import_stmt
set_stmt: var_name set_op set_right_side _NEWLINE
?set_right_side: test | spec_op

doc_string_stmt: doc_string _NEWLINE

// TODO: support more assignment operators?
set_op: "=" -> set
      | "+=" -> increment
      | "-=" -> decrement

return_stmt: "return" [test] _NEWLINE
abort_stmt: "abort" _NEWLINE
break_stmt: "break" _NEWLINE
continue_stmt: "continue" _NEWLINE
pass_stmt: "pass" _NEWLINE
log_stmt: "log" (expr | "(" expr ")") _NEWLINE
print_stmt: "print" (expr | "(" expr ")") _NEWLINE
priority_stmt: "priority" expr _NEWLINE
label_stmt: NAME":" _NEWLINE
global_stmt: "global" var_name _NEWLINE
import_stmt: "import" (package_name | string) _NEWLINE
expr_stmt: "(" test ")"

// --------------------------------

// A "spec" is a unifying concept for flows, actions, events
spec_operator: "match" -> match_spec
             | "await" -> await_spec
             | "start" -> start_spec
             | "stop" -> stop_spec
             | "activate" -> activate_spec
             | "send" -> send_spec

spec_op: spec_operator spec_expr
       | non_var_spec_expr

?spec_op_stmt: spec_op _NEWLINE
capture_ref: "as" var_name

// TODO: find a more elegant solution.
// NON-VAR branch
?non_var_spec_expr: non_var_spec_or
?non_var_spec_or: non_var_spec_and (_OR non_var_spec_and)+
        | non_var_spec_and
?non_var_spec_and: non_var_spec_atom (_AND non_var_spec_atom)+
         | non_var_spec_atom
?non_var_spec_atom: "(" non_var_spec_expr ")"
         | non_var_spec
?non_var_spec: spec_name [classic_arguments | simple_arguments] (spec_member)* [capture_ref] -> spec

// Arbitrary expressions with specs including "and" and "or" as operators
?spec_expr: spec_or
?spec_or: spec_and (_OR spec_and)+
        | spec_and
?spec_and: spec_atom (_AND spec_atom)+
         | spec_atom
?spec_atom: "(" spec_expr ")"
         | spec

// A spec includes name, arguments and potentially members access.
// Example members: .Finished(), .action
spec: spec_name [classic_arguments | simple_arguments] (spec_member)* [capture_ref]
    | var_name (spec_member)* [capture_ref]
spec_member: "." name [classic_arguments]

// Classic arguments use the syntax from python
classic_arguments: "(" [_argvalues] ")"
_argvalues: argvalue ("," argvalue)*
argvalue: expr
        | name "=" expr

// A simplified version of the arguments without parenthesis and ","
simple_arguments: simple_argvalue+
?simple_argvalue: expr
                | var_name "=" expr

// Compound statements
?compound_stmt: if_stmt | while_stmt | when_stmt
while_stmt: "while" test suite
if_stmt: _IF test suite elifs ["else" suite]
elifs: elif_*
elif_: _ELSE_IF test suite
when_stmt: "when" spec_expr suite orwhens ["else" suite]
orwhens: orwhen_*
orwhen_: _OR_WHEN spec_expr suite

// --------------------------------

// Test expression (used both for conditions and assignment)
test: or_test
?or_test: and_test (_OR and_test)*
?and_test: not_test_ (_AND not_test_)*
?not_test_: _NOT not_test_ -> not_test
         | comparison
?comparison: expr (comp_op expr)*
star_expr: "*" expr

expr: or_expr
?or_expr: xor_expr ("|" xor_expr)*
?xor_expr: and_expr ("^" and_expr)*
?and_expr: shift_expr ("&" shift_expr)*
?shift_expr: arith_expr (_shift_op arith_expr)*
?arith_expr: term (_add_op term)*
?term: factor (_mul_op factor)*
?factor: _unary_op factor | power

!_unary_op: "+"|"-"|"~"
!_add_op: "+"|"-"
!_shift_op: "<<"|">>"
!_mul_op: "*"|"@"|"/"|"%"|"//"
!comp_op: "<"|">"|"=="|">="|"<="|"<>"|"!="|_IN|_NOT _IN|_IS|_IS _NOT

?power: atom_expr ("**" factor)?
?atom_expr: name "(" [arguments] ")"    -> funccall
          | atom_expr "[" subscriptlist "]"  -> getitem
          | atom_expr "." attr_name          -> getattr
          | atom_expr "." method_name "(" [arguments] ")" -> method
          | "[" _exprlist? "]"  -> list
          | "{" _dict_exprlist? "}" -> dict
          | "{" _exprlist "}" -> set
          | atom

_exprlist: atom_expr (","  atom_expr)* [","]
_dict_exprlist: (key_value | "**" expr) ("," (key_value | "**" expr))* [","]
key_value: test ":"  test

arguments: argvalue ("," argvalue)*  ("," [ starargs | kwargs])?
         | starargs
         | kwargs

starargs: stararg ("," stararg)* ("," argvalue)* ["," kwargs]
stararg: "*" test
kwargs: "**" test ("," argvalue)*

?subscriptlist: subscript
              | subscript (("," subscript)+ [","] | ",") -> subscript_tuple
?subscript: test | ([test] ":" [test] [sliceop]) -> slice
sliceop: ":" [test]

// Setting the priority for this rule is important!
// When using a variable in the right side of an assignment, it will be considered an expression
// rather than a spec (both expressions and specs can be just a variable).
?atom: var_name -> var
     | number
     | string
     | doc_string
     | "None"    -> const_none
     | "True"    -> const_true
     | "False"   -> const_false

spec_name: name+
         | "(" name+ ")"

// --------------------------------

name: NAME
attr_name: NAME
decorator_name: NAME
method_name: NAME
var_name: VAR_NAME
package_name: NAME ("." NAME)*
comment: COMMENT

// Primitive types
// TODO: add more primitive types

number: DEC_NUMBER | FLOAT_NUMBER
string: STRING
doc_string: LONG_STRING

// --------------------------------

// LEXER rules below

// High priority keywords

// TODO: Create a positive lookbehind for whitespace characters: e.g. with /(?<=\s)
_FLOW.1: /(?<!\.)flow(?!(\s*\(|[a-zA-Z0-9_]))/

_IF.1: /if/
_ELSE_IF.1: /(elif|else\s+if)/
_OR_WHEN.1: /(or ?when)/
_AND.1: /(and[ \t]|(\r?\n[\t ]*)+and[ \t])/
_OR.1: /(or[ \t]|(\r?\n[\t ]*)+or[ \t]+(?!when))/
_IS.1: /is/
_IN.1: /in/
_NOT.1: /not/

// Names

NAME: /[^\W\d]\w*/
VAR_NAME: /\$[^\W\d]\w*/

COMMENT: /#[^\n]*/
_NEWLINE: (/\r?\n[\t ]*/)+

// Primitive values

STRING: /i?("(?!"").*?(?<!\\)(\\\\)*?"|i?'(?!'').*?(?<!\\)(\\\\)*?')/i
LONG_STRING: /(""".*?(?<!\\)(\\\\)*?"""|'''.*?(?<!\\)(\\\\)*?''')/is

_SPECIAL_DEC: "0".."9"        ("_"?  "0".."9"                       )*
DEC_NUMBER:   "1".."9"        ("_"?  "0".."9"                       )*
          |   "0"             ("_"?  "0"                            )* /(?![1-9])/
_EXP: ("e"|"E") ["+" | "-"] _SPECIAL_DEC
DECIMAL: "." _SPECIAL_DEC | _SPECIAL_DEC "." _SPECIAL_DEC?
FLOAT_NUMBER.2: _SPECIAL_DEC _EXP | DECIMAL _EXP?


%declare _INDENT _DEDENT
%ignore " "

// TODO: come up with a solution to keep the comments for custom instructions
%ignore COMMENT
